package br.ufc.great.dtxtester;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Vector;

import javax.swing.DefaultListModel;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextPane;
import javax.swing.UIManager;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;

import org.bounce.text.xml.XMLEditorKit;
import org.bounce.text.xml.XMLStyleConstants;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;

import br.ufc.great.client.DTXInsertOperation;
import br.ufc.great.client.DTXSite;
import br.ufc.great.client.DTXTransaction;
import br.ufc.great.dtxtester.operation.InsertOperation;
import br.ufc.great.dtxtester.operation.InsertOperations;
import br.ufc.great.dtxtester.operation.QueryOperation;
import br.ufc.great.dtxtester.operation.QueryOperations;
import br.ufc.great.xml.XMLDocument;

/**
 * 
 * @author Leonardo Oliveira Moreira
 *
 */
public class DTXTester extends JFrame implements WindowListener {

	/**
	 * 
	 */
	private static final long serialVersionUID = -3458575618768363093L;

	private Vector<Client> clients;
	private int numberActiveClients = 0;
	private long initExperimentTime = 0;
	private int numberDeadlock = 0;
	
	{
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        }
        catch (Exception e) {
        	e.printStackTrace();
        	System.exit(0);
        }
	}
	
	@Override
	public void windowActivated(WindowEvent e) {

	}

	@Override
	public void windowClosed(WindowEvent e) {
		
	}

	@Override
	public void windowClosing(WindowEvent e) {
		System.exit(0);
	}

	@Override
	public void windowDeactivated(WindowEvent e) {

	}

	@Override
	public void windowDeiconified(WindowEvent e) {
	
	}

	@Override
	public void windowIconified(WindowEvent e) {
		
	}

	@Override
	public void windowOpened(WindowEvent e) {
		
	}

	public DTXTester() {
        jlSite = new javax.swing.JLabel();
        jlSites = new javax.swing.JList(new DefaultListModel());
        jlNumberClients = new javax.swing.JLabel();
        jtfNumberClients = new javax.swing.JTextField();
        jlNumberTransactions = new javax.swing.JLabel();
        jtfNumberTransactions = new javax.swing.JTextField();
        jlNumberOperations = new javax.swing.JLabel();
        jtfNumberOperations = new javax.swing.JTextField();
        jlUpdateOperations = new javax.swing.JLabel();
        jlUpdateTransactions = new javax.swing.JLabel();
        jtfUpdateTransactions = new javax.swing.JTextField();
        jtfUpdateOperations = new javax.swing.JTextField();
        jlPercentUT = new javax.swing.JLabel();
        jlPercentUO = new javax.swing.JLabel();
        jlExtension = new javax.swing.JLabel();
        jlTransactionsInterval = new javax.swing.JLabel();
        jtfTransactionsInterval = new javax.swing.JTextField();
        jlSite.setText("Sites:");

        jlNumberClients.setText("Number of clients:");
        jlNumberTransactions.setText("Number of transactions:");
        jlNumberOperations.setText("Number of operations:");
        jlUpdateOperations.setText("Update operations:");
        jlUpdateTransactions.setText("Update transactions:");
        jlTransactionsInterval.setText("Delay between transactions:");
        jlPercentUT.setText("%");
        jlPercentUO.setText("%");
        jlExtension.setText("ms");

    	jtfNumberClients.setText("0");
    	jtfNumberTransactions.setText("0");
    	jtfUpdateTransactions.setText("0");
    	jtfNumberOperations.setText("0");        	
    	jtfUpdateOperations.setText("0");
    	jtfTransactionsInterval.setText("0");
    	jtfNumberClients.setEditable(false);
    	jtfNumberTransactions.setEditable(false);
    	jtfUpdateTransactions.setEditable(false);
    	jtfNumberOperations.setEditable(false);  	
    	jtfUpdateOperations.setEditable(false);
    	jtfTransactionsInterval.setEditable(false);
	}
	
	public void build() {
		setTitle("DTXTester - [Distributed Transaction on XML]");
		setSize(640, 480);
        Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
        setLocation((dimension.width - getSize().width) / 2, (dimension.height - getSize().height) / 2); 

        addWindowListener(this);

        jmbMenuBar = new JMenuBar();
        
        jmFile = new JMenu("File");
        jmExperiment = new JMenu("Experiment");
        
        jmiFileLoad = new JMenuItem("Load...");
        jmiFileExit = new JMenuItem("Exit");
        
        jmiExperimentCreate = new JMenuItem("Create");
        jmiExperimentStart = new JMenuItem("Start");
        
        jmFile.add(jmiFileLoad);
        jmFile.addSeparator();
        jmFile.add(jmiFileExit);
        
        jmExperiment.add(jmiExperimentCreate);
        jmExperiment.addSeparator();
        jmExperiment.add(jmiExperimentStart);
        
        jmbMenuBar.add(jmFile);
        jmbMenuBar.add(jmExperiment);
        
        setJMenuBar(jmbMenuBar);
        
        jmiExperimentStart.setEnabled(false);
        
        jtpLog = new JTextPane();
        JScrollPane jspLog = new JScrollPane(jtpLog);

        jepContent = new JEditorPane();
        JScrollPane jspContent = new JScrollPane(jepContent);
        
        JPanel jpLog = new JPanel(new BorderLayout());
        jpLog.add(jspLog);
        JPanel jpParameters = new JPanel(null);

        jlSite.setBounds(10, 10, 50, 20);
        jspSites = new JScrollPane(jlSites);
        jspSites.setBounds(10, 30, 215, 185);
        
        jlNumberClients.setBounds(10, 225, 120, 20);
        jlNumberTransactions.setBounds(10, 250, 120, 20);
        jlUpdateTransactions.setBounds(10, 275, 120, 20);
        jlPercentUT.setBounds(215, 275, 120, 20);
        jlNumberOperations.setBounds(10, 300, 120, 20);
        jlUpdateOperations.setBounds(10, 325, 120, 20);
        jlPercentUO.setBounds(215, 325, 120, 20);
        jlTransactionsInterval.setBounds(10, 350, 140, 20);
        jlExtension.setBounds(215, 350, 120, 20);
        
        jtfNumberClients.setBounds(150, 225, 75, 20);
        jtfNumberTransactions.setBounds(150, 250, 75, 20);
        jtfUpdateTransactions.setBounds(150, 275, 60, 20);
        jtfNumberOperations.setBounds(150, 300, 75, 20);
        jtfUpdateOperations.setBounds(150, 325, 60, 20);
        jtfTransactionsInterval.setBounds(150, 350, 60, 20);

        jpParameters.add(jlSite);
        jpParameters.add(jspSites);

        jpParameters.add(jlNumberClients);
        jpParameters.add(jlNumberTransactions);
        jpParameters.add(jlUpdateTransactions);
        jpParameters.add(jlPercentUT);
        jpParameters.add(jlNumberOperations);
        jpParameters.add(jlUpdateOperations);
        jpParameters.add(jlPercentUO);
        jpParameters.add(jlTransactionsInterval);
        jpParameters.add(jlExtension);
        
        jpParameters.add(jtfNumberClients);
        jpParameters.add(jtfNumberTransactions);
        jpParameters.add(jtfUpdateTransactions);
        jpParameters.add(jtfNumberOperations);
        jpParameters.add(jtfUpdateOperations);
        jpParameters.add(jtfTransactionsInterval);
        
        JPanel jpContent = new JPanel(new BorderLayout());
        jpContent.add(jspContent);
        
        JSplitPane jspTopBottom = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
        JSplitPane jspLeftRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
        
        jspLeftRight.setLeftComponent(jpParameters);
        jspLeftRight.setRightComponent(jpContent);
        jspLeftRight.setResizeWeight(0.45);
        jspTopBottom.setTopComponent(jspLeftRight);
        jspTopBottom.setBottomComponent(jpLog);
        jspTopBottom.setResizeWeight(0.95);

		StyledDocument styledDoc = jtpLog.getStyledDocument();
        styleTime = styledDoc.addStyle("time", null);
        StyleConstants.setBold(styleTime, true);
        StyleConstants.setForeground(styleTime, Color.GRAY);
        styleText = styledDoc.addStyle("text", null);
        StyleConstants.setForeground(styleText, Color.BLACK);
        styleError = styledDoc.addStyle("styleError", null);
        StyleConstants.setForeground(styleError, Color.RED);
        styleName = styledDoc.addStyle("styleName", null);
        StyleConstants.setForeground(styleName, Color.BLACK);
        StyleConstants.setBold(styleName, true);
        jtpLog.setEditable(false);

        jepContent.setEditable(false);
    	
        XMLEditorKit kit = new XMLEditorKit(true);
        kit.setWrapStyleWord(true);
        jepContent.setEditorKit(kit);
        jepContent.setFont(new Font("Courier", Font.PLAIN, 12));
        javax.swing.text.Document doc = jepContent.getDocument();
        doc.putProperty(PlainDocument.tabSizeAttribute, 4);
        doc.putProperty(org.bounce.text.xml.XMLDocument.AUTO_INDENTATION_ATTRIBUTE, true);
        doc.putProperty(org.bounce.text.xml.XMLDocument.TAG_COMPLETION_ATTRIBUTE, true);
        kit.setStyle(XMLStyleConstants.ATTRIBUTE_NAME, new Color(255, 0, 0), Font.BOLD);

        jmiFileLoad.addActionListener(
        	new ActionListener() {
        		public void actionPerformed(ActionEvent e) {
        			loadExperiment();
        		}
        	}
        );
        
        jmiExperimentCreate.addActionListener(
        	new ActionListener() {
        		public void actionPerformed(ActionEvent e) {
        			createExperiment();
        		}
        	}
        );

        jmiExperimentStart.addActionListener(
        	new ActionListener() {
        		public void actionPerformed(ActionEvent e) {
        			startExperiment();
        		}
        	}
        );

        add(jspTopBottom);
	}
	
	private void createExperiment() {
		CreateExperimentDialog dialog = new CreateExperimentDialog(this);
		dialog.setVisible(true);
	}
	
	public void loadExperiment(File file) {
		//int numberClients = Integer.parseInt(jtfNumberClients.getText());
		//int percentUpdateTransactions = Integer.parseInt(jtfUpdateTransactions.getText());
		//int numberTransactions = Integer.parseInt(jtfNumberTransactions.getText());
		//int numberUpdateTransactions = (percentUpdateTransactions * numberTransactions) / 100;
		//int numberReadTransactions = numberTransactions - numberUpdateTransactions;
		//int numberOperations = Integer.parseInt(jtfNumberOperations.getText());
		//int percentUpdateOperations = Integer.parseInt(jtfUpdateOperations.getText());
		//int numberUpdateOperations = (percentUpdateOperations * numberOperations) / 100;
		//int numberReadOperations = numberOperations - numberUpdateOperations;
		//long transactionsInterval = Long.parseLong(jtfTransactionsInterval.getText());
		try {
			SAXBuilder saxBuilder = new SAXBuilder();
			Document document = saxBuilder.build(file);
		    Element experiment = document.getRootElement();
		    // Preenche os parâmetros
		    Element parameters = experiment.getChild("parameters");
		    jtfNumberClients.setText(parameters.getChild("clients").getValue());
		    jtfNumberTransactions.setText(parameters.getChild("transactions").getAttributeValue("number"));
		    jtfTransactionsInterval.setText(parameters.getChild("transactions").getAttributeValue("delayBetweenTransactions"));
		    jtfUpdateTransactions.setText(parameters.getChild("transactions").getChild("updateTransactions").getAttributeValue("percent"));
		    jtfNumberOperations.setText(parameters.getChild("operations").getAttributeValue("number"));
		    jtfUpdateOperations.setText(parameters.getChild("operations").getChild("updateOperations").getAttributeValue("percent"));
		    //Cria a lista de sites
		    Vector<Site> sites = new Vector<Site>();
		    List sitesList = experiment.getChild("sites").getChildren();
		    for (int i = 0; sitesList != null && i < sitesList.size(); i++) {
		    	Element siteItem = (Element) sitesList.get(i);
		    	String id = siteItem.getAttributeValue("id");
		    	String host = siteItem.getAttributeValue("host");
		    	int port = Integer.parseInt(siteItem.getAttributeValue("port"));
		    	Site site = new Site(id, host, port);
		    	sites.add(site);
		    }
	    	DefaultListModel listModel = (DefaultListModel) jlSites.getModel();
	    	listModel.removeAllElements();
    	  	for (int i = 0; i < sites.size(); i++) {
    	  		Site site = sites.elementAt(i);
    	  		listModel.addElement(site.getHost() + ":" + site.getPort());
    	  	}
		    //Cria a lista de clientes
		    clients = new Vector<Client>();
		    List clientsList = experiment.getChild("clients").getChildren();
		    for (int i = 0; clientsList != null && i < clientsList.size(); i++) {
		    	Element clientItem = (Element) clientsList.get(i);
		    	String name = clientItem.getAttributeValue("name");
		    	String siteId = clientItem.getAttributeValue("site");
		    	long transactionsInterval = Long.parseLong(clientItem.getAttributeValue("transactionsInterval"));
		    	Site site = null;
		    	for (int j = 0; j < sites.size(); j++)
		    		if (sites.get(j).getId().equalsIgnoreCase(siteId)) {
		    			site = sites.elementAt(j);
		    			break;
		    		}
		    	//Cria a lista de transações do cliente
		    	Vector<Transaction> transactions = new Vector<Transaction>();
		    	List transactionsList = clientItem.getChild("transactions").getChildren();
		    	for (int j = 0; transactionsList != null && j < transactionsList.size(); j++) {
		    		Element transactionItem = (Element) transactionsList.get(j);
		    		String id = transactionItem.getAttributeValue("id");
		    		List operationsList = transactionItem.getChild("operations").getChildren();
		    		//Cria a lista de operações da transação
		    		Vector<Operation> operations = new Vector<Operation>();
		    		for (int k = 0; operationsList != null && k < operationsList.size(); k++) {
		    			Element operationItem = (Element) operationsList.get(k);
		    			String sitesId = operationItem.getAttributeValue("sites");

		    			Vector<Site> sitesOperation = new Vector<Site>();
		    			if (sitesId.indexOf(";") == -1) {
					    	for (int w = 0; w < sites.size(); w++)
					    		if (sites.get(w).getId().equalsIgnoreCase(sitesId)) {
					    			sitesOperation.add(sites.elementAt(w));
					    			break;
					    		}
		    			}
		    			else {
		    				String[] sitesIdArray = sitesId.split("[;]");
		    				for (int z = 0; sitesIdArray != null && z < sitesIdArray.length; z++) {
						    	for (int w = 0; w < sites.size(); w++)
						    		if (sites.get(w).getId().equalsIgnoreCase(sitesIdArray[z])) {
						    			sitesOperation.add(sites.elementAt(w));
						    			break;
						    		}
		    				}
		    			}
		    			
		    			String strType = operationItem.getAttributeValue("type");
		    			String fileName = operationItem.getAttributeValue("fileName");
		    			String operationId = operationItem.getAttributeValue("id");
		    			int type = Operation.QUERY;
		    			if (strType.equalsIgnoreCase("insert"))
		    				type = Operation.INSERT;
		    			if (strType.equalsIgnoreCase("delete"))
		    				type = Operation.DELETE;
		    			operations.add(new Operation(sitesOperation, type, fileName, operationId));
		    		}
		    		transactions.add(new Transaction(id, operations));
		    	}
		    	clients.add(new Client(name, site, transactions, transactionsInterval));
		    }
		    setTitle("DTXTester - [Distributed Transaction on XML] - " + file.getName());
		    XMLDocument doc = new XMLDocument(document, file.getName());
		    jepContent.setText(doc.toString());
	        
	        jmiExperimentStart.setEnabled(true);
		}
		catch (JDOMException e) {
			addLog(e.getMessage(), true);
		}
		catch (IOException e) {
			addLog(e.getMessage(), true);
		}
	}
	
	private void loadExperiment() {
		final JFileChooser fileChooser = new JFileChooser();
		
        int result = fileChooser.showOpenDialog(null);
        
        if (result == JFileChooser.APPROVE_OPTION) {
        	File file = fileChooser.getSelectedFile(); 
        	loadExperiment(file);
        }
        else {
        	
        }
	}
	
	private void startExperiment() {
		addLog("Preparing to process...", false);
		Vector<DTXClient> dtxClients = new Vector<DTXClient>();
		try {
			QueryOperations queryOperations = new QueryOperations("query-operations.xml");
			InsertOperations insertOperations = new InsertOperations("insert-operations.xml");

			for (int k = 0; k < clients.size(); k++) {
				Client client = clients.elementAt(k);
				Site site = client.getSite();
				Vector<Transaction> transactions = client.getTransactions();
				Vector<DTXTransaction> dtxTransactions = new Vector<DTXTransaction>();
				for (int i = 0; i < transactions.size(); i++) {
					Transaction transaction = transactions.elementAt(i);
					DTXTransaction dtxTransaction = new DTXTransaction(transaction.getId());
					Vector<Operation> operations = transaction.getOperations();
					for (int j = 0; j < operations.size(); j++) {
						Operation operation = operations.elementAt(j);
						Vector<Site> sites = operation.getSites();
						Vector<DTXSite> dtxSites = new Vector<DTXSite>();
						for (int w = 0; w < sites.size(); w++)
							dtxSites.add(new DTXSite(sites.elementAt(w).getHost(), sites.elementAt(w).getPort()));
						switch (operation.getType()) {
							case Operation.QUERY : {
								QueryOperation queryOperation = queryOperations.getOperation(operation.getId());
								dtxTransaction.addQueryOperation(dtxSites, queryOperation.getQuery());
								break;
							}
							case Operation.INSERT : {
								InsertOperation insertOperation = insertOperations.getOperation(operation.getId());
								dtxTransaction.addInsertOperation(dtxSites, insertOperation.getQuery(), DTXInsertOperation.INTO, insertOperation.getConstructor());
								break;
							}
							case Operation.DELETE : {
								break;
							}
						}
					}
					dtxTransactions.add(dtxTransaction);
				}
				dtxClients.add(new DTXClient(client.getName(), new DTXSite(site.getHost(), site.getPort()), dtxTransactions, client.getTransactionsInterval()));
			}
		}
		catch (IOException e) {
			addLog(e.getMessage(), true);
		}

		numberActiveClients = 0;
		numberDeadlock = 0;
		initExperimentTime = System.currentTimeMillis();
		addLog("Process started...", false);
		for (int i = 0; i < dtxClients.size(); i++) {
			DTXClient client = dtxClients.elementAt(i);
			ClientSimulator sim = new ClientSimulator(this, client);
			addLog("Creating client " +  client.getName() + "...", false);
			sim.start();
			numberActiveClients++;			
			addLog("Client " +  client.getName() + " sending transactions to " + clients.elementAt(i).getSite().getHost() + ":" + clients.elementAt(i).getSite().getPort(), false);
		}
		System.out.println("number cliente:"+numberActiveClients);
	}
	
	public void endClient(DTXClient client) {
		synchronized (this) {
			numberActiveClients--;
			System.out.println("saiu:"+numberActiveClients);
			addLog(client.getName(), "executed in " + client.getExecutionTime() + " seconds", false);
			numberDeadlock += client.getNumberDeadlocks();
			if (numberActiveClients == 0) {
				long endExperimentTime = System.currentTimeMillis();
				String experimentTime = "";
				if ((endExperimentTime - initExperimentTime) < 1000) {
					experimentTime += "0." + (endExperimentTime - initExperimentTime) % 1000;
				}
				else {
					experimentTime += ((endExperimentTime - initExperimentTime) / 1000) + "." + (endExperimentTime - initExperimentTime) % 1000; 
				}
				addLog("Experiment executed in " + experimentTime + " seconds with " + numberDeadlock + " deadlocks", false);
				//ShowResultsDialog results = new ShowResultsDialog(experimentTime, String.valueOf(client.getNumberDeadlocks()));
				//results.setVisible(true);
			}
		}
	}

	public synchronized void addLog(String message, boolean error) {
        StyledDocument styledDoc = jtpLog.getStyledDocument();
		SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM hh:mm:ss.SSS");
		String date = "[" + dateFormat.format(new Date()) + "] ";
        try {
        	styledDoc.insertString(0, date, styleTime);
        	if (error)
        		styledDoc.insertString(date.length(), message + "\n", styleError);
        	else
        		styledDoc.insertString(date.length(), message + "\n", styleText);
        	jtpLog.setCaretPosition(0);
        } 
        catch (BadLocationException e) {
            
        }
	}
	
	public synchronized void addLog(String name, String message, boolean error) {
        StyledDocument styledDoc = jtpLog.getStyledDocument();
		SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM hh:mm:ss.SSS");
		String date = "[" + dateFormat.format(new Date()) + "] ";
        try {
        	styledDoc.insertString(0, date, styleTime);
        	styledDoc.insertString(date.length(), name + ": ", styleName);
        	if (error)
        		styledDoc.insertString((name.length() + date.length() + 2), message + "\n", styleError);
        	else
        		styledDoc.insertString((name.length() + date.length() + 2), message + "\n", styleText);
        	jtpLog.setCaretPosition(0);
        } 
        catch (BadLocationException e) {
            
        }
	}
	
	public static void main(String[] args) {
		DTXTester app = new DTXTester();
		app.build();
		app.setVisible(true);
	}
	
	private JMenuBar jmbMenuBar;
	private JMenu jmFile, jmExperiment;
	private JMenuItem jmiFileLoad, jmiFileExit;
	private JMenuItem jmiExperimentCreate, jmiExperimentStart;
	
	private JTextPane jtpLog;
	private JEditorPane jepContent;
	
	private Style styleText, styleTime, styleError, styleName;
	
    private javax.swing.JList jlSites;
    private javax.swing.JLabel jlNumberClients;
    private javax.swing.JLabel jlNumberOperations;
    private javax.swing.JLabel jlNumberTransactions;
    private javax.swing.JLabel jlPercentUO;
    private javax.swing.JLabel jlPercentUT;
    private javax.swing.JLabel jlExtension;
    private javax.swing.JLabel jlSite;
    private javax.swing.JLabel jlUpdateOperations;
    private javax.swing.JLabel jlUpdateTransactions;
    private javax.swing.JLabel jlTransactionsInterval;
    private javax.swing.JTextField jtfNumberClients;
    private javax.swing.JTextField jtfNumberOperations;
    private javax.swing.JTextField jtfNumberTransactions;
    private javax.swing.JTextField jtfUpdateOperations;
    private javax.swing.JTextField jtfUpdateTransactions;
    private javax.swing.JTextField jtfTransactionsInterval;
    private javax.swing.JScrollPane jspSites;

}
